#!/usr/bin/env python
#
# device.py
#
#  Copyright (c) 2006-2010  Yusuke Shinyama <yusuke at cs dot nyu dot edu>
#  
#  Permission is hereby granted, free of charge, to any person
#  obtaining a copy of this software and associated documentation
#  files (the "Software"), to deal in the Software without
#  restriction, including without limitation the rights to use,
#  copy, modify, merge, publish, distribute, sublicense, and/or
#  sell copies of the Software, and to permit persons to whom the
#  Software is furnished to do so, subject to the following
#  conditions:
#  
#  The above copyright notice and this permission notice shall be
#  included in all copies or substantial portions of the Software.
#  
#  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
#  KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
#  WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
#  PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
#  COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
#  OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
#  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

import sys
import codecs
import curses
import array
from style import Style


##  Font
##
class Font(object):
  
  def __init__(self, size=1, ascent=1, descent=0, blank_width=1):
    self.size = size
    self.ascent = ascent
    self.descent = descent
    self.blank_width = blank_width
    return

  def __repr__(self):
    return '<Font size=%r>' % self.size

  def char_size(self, s):
    return (len(s), 1)
  

##  Device
##
class Device(object):

  media_type = None
  
  default_style = Style(

    font = Font(),
    font_size = 0,
    text_align = 'left',
    object_align = 'left',
    vertical_align = 'bottom',
    color = 'black',
    line_space = 0,
    blank_line_height = 1,
    para_space = 0,
    text_indent = 0,
    margin_top = 0,
    margin_left = 0,
    margin_right = 0,
    margin_bottom = 0,
    padding_top = 0,
    padding_left = 0,
    padding_right = 0,
    padding_bottom = 0,
    border_width_top = 0,
    border_width_left = 0,
    border_width_right = 0,
    border_width_bottom = 0,
    table_border = 0,
    list_padding = 0,
    width = 0,
    height = 0,
    top = 0,
    left = 0,
    right = 0,
    bottom = 0,
    position = None,

    halign = 'left',
    valign = 'middle',

    wrap_text = True,
    xscale = 1.0/16,
    yscale = 1.0/32,
    base_pt = 0.1
    )

  def __init__(self, width, height):
    self.width = width
    self.height = height
    return
  
  def set_title(self, s):
    raise NotImplementedError

  def get_font(self, args):
    raise NotImplementedError
  
  def draw_text(self, font, x, y, s, attr=None):
    raise NotImplementedError

  def draw_image(self, x, y, w, h, src, alt, attr=None):
    raise NotImplementedError

  def draw_rect(self, x, y, w, h, attr=None):
    raise NotImplementedError

  def draw_hline(self, x, y, w, attr=None):
    raise NotImplementedError


##  DebugDevice
##
class DebugDevice(Device):
  
  media_type = 'screen'

  def __init__(self, width, height, fp=sys.stdout):
    Device.__init__(self, width, height)
    self.fp = fp
    return
  
  def get_font(self, args):
    print >>self.fp, 'get_font:', args
    return Font()
    
  def set_title(self, s):
    print >>self.fp, 'set_title:', s
    return
  
  def draw_text(self, font, x, y, s, attr=None):
    print >>self.fp, 'draw_text:', font, x, y, s, attr
    return

  def draw_image(self, x, y, w, h, src, alt, attr=None):
    print >>self.fp, 'draw_image:', x, y, w, h, src, alt, attr
    return

  def draw_rect(self, x, y, w, h, attr=None):
    print >>self.fp, 'draw_rect:', x, y, w, h
    return

  def draw_hline(self, x, y, w, attr=None):
    print >>self.fp, 'draw_hline:', x, y, w
    return


##  JPCursesDevice
##
class JPCursesDevice(Device):

  media_type = 'screen'

  @staticmethod
  def is_wide(c): return u'\u2000' <= c

  # JPFont
  class JPFont(Font):

    TRANS = {
      149: u'+',
      160: u' ',
      183: u'*',
      169: u'(C)',
      }
    
    def __init__(self, attr, size=1, ascent=1, descent=0, blank_width=1):
      Font.__init__(self, size=size, ascent=ascent,
                    descent=descent, blank_width=blank_width)
      self.attr = attr
      return
    
    def translate(self, s):
      return s.translate(self.TRANS)

    def char_size(self, s):
      s = s.translate(self.TRANS)
      n = len(s) + sum( 1 for c in s if JPCursesDevice.is_wide(c) )
      return (n, 1)

  default_style = Style(parent=Device.default_style, font=JPFont(0))

  ##
  
  def __init__(self, width, height, window, encoding='euc-jp'):
    Device.__init__(self, width, height)
    self.window = window
    self.encoder = codecs.getencoder(encoding)
    self.font = JPCursesDevice.JPFont(0)
    self.clear()
    curses.init_pair(1, curses.COLOR_CYAN, curses.COLOR_BLACK)
    curses.init_pair(2, curses.COLOR_BLACK, curses.COLOR_GREEN)
    return

  def clear(self):
    self.lines = [ (array.array('u', u' '*self.width), array.array('i', [0]*self.width))
                   for _ in xrange(self.height) ]
    self.changed = [ True for _ in xrange(self.height) ]
    return

  def splitchars(self, s, width):
    i = 0
    (n,i0) = (0,i)
    while i < len(s):
      (w,_) = self.font.char_size(s[i])
      if width < n+w:
        yield s[i0:i]
        (n,i0) = (0,i)
      n += w
      i += 1
    if n:
      yield s[i0:]
    return
  
  def _draw_chars(self, x, y, s, a=0):
    if y < 0 or self.height <= y or self.width <= x: return
    s = unicode(s)
    while x < 0:
      if not s: return
      x += 1
      if self.is_wide(s[0]):
        x += 1
      s = s[1:]
    if not s: return
    (line,attr) = self.lines[y]
    self.changed[y] = True
    if line[x] == u'\000':
      line[x-1] = u' '
      attr[x-1] = 0
    for c in s:
      line[x] = c
      attr[x] = a
      x += 1
      if self.width <= x: break
      if self.is_wide(c):
        line[x] = u'\000'
        x += 1
        if self.width <= x: break
    if x < len(line) and line[x] == u'\000':
      line[x] = u' '
      attr[x] = 0
    return

  def get_font(self, args):
    if args:
      return JPCursesDevice.JPFont(1)
    else:
      return JPCursesDevice.JPFont(0)
  
  def draw_text(self, font, x, y, s, attr=None):
    cattr = 0
    if font.attr:
      if attr == 'focused':
        cattr = curses.color_pair(2) + curses.A_UNDERLINE
      else:
        cattr = curses.color_pair(1) + curses.A_UNDERLINE
    return self._draw_chars(int(x), int(y), font.translate(unicode(s)), cattr)

  def draw_image(self, x, y, w, h, src, alt, attr=None):
    if not w or not h:
      pass
    elif w <= 3 and h <= 1:
      self._draw_chars(x, y, '*' * w)
    elif h <= 1:
      self._draw_chars(x, y, '=' * w)
      if alt and 3 <= w:
        for line in self.splitchars(alt, w-2):
          self._draw_chars(x, y, '='+line)
          break
    else:
      self.draw_rect(x, y, w, h)
      if alt and 3 <= w:
        for line in self.splitchars(alt, w):
          self._draw_chars(x, y, line)
          y += 1
          h -= 1
          if not h: break
    return

  def draw_rect(self, x, y, w, h, attr=None):
    (x,y) = (int(x), int(y))
    (w,h) = (int(w), int(h))
    self._draw_chars(x, y, '+'+'-'*(w-2)+'+')
    for i in xrange(y+1, y+h-1):
      self._draw_chars(x, i, '|')
      self._draw_chars(x+w-1, i, '|')
    self._draw_chars(x, y+h-1, '+'+'-'*(w-2)+'+')
    return

  def draw_hline(self, x, y, w, attr=None):
    (x,y,w) = (int(x), int(y), int(w))
    self._draw_chars(x, y, '_'*w)
    return
  
  def paint(self, x0=0, y0=0):
    sy = 0
    y = y0
    while sy < self.height and y < len(self.lines):
      if self.changed[sy]:
        self.changed[sy] = False
        (line,attr) = self.lines[y]
        (sx0,s0,a0) = (0, '', 0)
        sx = 0
        x = x0
        while sx < self.width and x < len(line):
          c = line[x]
          if c != u'\000':
            a = attr[x]
            if a != a0:
              self.window.addstr(sy, sx0, self.encoder(s0, 'replace')[0], a0)
              (sx0,s0,a0) = (sx, '', a)
            s0 += c
          sx += 1
          x += 1
        if s0:
          self.window.addstr(sy, sx0, self.encoder(s0, 'replace')[0], a0)
      sy += 1
      y += 1
    return
